
package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.COMPARTMENT_ROLE_BASE;
import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_TITLE_LABEL;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Set;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.Label;
import xj.graph2d.RectangleShape;
import xj.lang.CodeWriter;

public class CompartmentalRectangleShape 
  extends RectangleShape 
  implements HorizontalCompartmentsShape {

  public CompartmentalRectangleShape() {
    this(null, false, false);
  }

  public CompartmentalRectangleShape(boolean createComp) {
    this(null, false, createComp);
  }

  public CompartmentalRectangleShape(float[] dividers) {
    this(dividers, false, false);
  }

  public CompartmentalRectangleShape(float[] dividers, boolean filled) {
    this(dividers, filled, false);
  }

  public CompartmentalRectangleShape(float[] dividers, boolean filled,
				     boolean createComp) {
    super(filled, createComp);
    adapter = new HorizontalCompartmentsAdapter(this);
    adapter.setHorizontalDividers(dividers);
    adapter.setHighLightCompartments(true);
  }

  public CompartmentalRectangleShape(float x1, float y1, float x2, float y2,
				     float[] dividers) {
    this(x1, y1, x2, y2, dividers, false, false);
  }

  public CompartmentalRectangleShape(float x1, float y1, float x2, float y2,
				     float[] dividers, boolean filled) {
    this(x1, y1, x2, y2, dividers, filled, false);
  }

  public CompartmentalRectangleShape(float x1, float y1, float x2, float y2,
				     float[] dividers, boolean filled, 
				     boolean createComp) {
    super(x1, y1, x2, y2, filled, createComp);
    adapter = new HorizontalCompartmentsAdapter(this);
    adapter.setHorizontalDividers(dividers);
    adapter.setHighLightCompartments(true);
  }

  @Override
  public DrawShape makeInstance() {
    return new CompartmentalRectangleShape(null, filled, comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s)
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof CompartmentalRectangleShape) {
      CompartmentalRectangleShape crect = (CompartmentalRectangleShape) s;
      if (adapter != null) {
	adapter.copyDividers(crect.adapter);
      }
    }
  }

  @Override
  public void draw(Graphics2D g2) {
    super.draw(g2);
    if (adapter != null) {
      adapter.drawHorizontalDividers(g2);
    }
  }

  protected void fillBackground(Graphics2D g2, float x, float y, float w, float h) {
    Color c = g2.getColor();
    if (adapter != null && 
	adapter.isHighLightCompartments()) { 
      Color bgcolor1 = c;
      if (attr != null) {
	bgcolor1 = attr.getFillColor();
      }
      Shape s1 = g2.getClip();
      Shape s2 = getOutlineShape();
      if (s2 != null) {
	g2.setClip(s2);
      }
      adapter.drawHighLightCompartments(g2, bgcolor1, x, y, w, h);
      g2.setClip(s1);
    } else {
      if (attr != null) {
	g2.setColor(attr.getFillColor());
      }
      g2.fill(new Rectangle2D.Float(x, y, w, h));
    }
    g2.setColor(c);
  }

  @Override
  public void drawSubShapes(Graphics2D g2, 
			    int mode, 
			    DrawShape selectedShape,
			    Set<DrawShape> selectionSet,
			    boolean reverseOrder) {
    float[] dividers = getHorizontalDividers();
    if (dividers == null) {
      super.drawSubShapes(g2, mode, selectedShape, selectionSet, reverseOrder);
    } else {
      adapter.drawSubShapes(g2, mode, selectedShape, selectionSet, reverseOrder);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2) {
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      g2.setColor(attr.getPenColor());
    } else {
      g2.setColor(DrawAttr.getDefaultPenColor());
    }
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    g2.draw(new Rectangle2D.Float(x, y, w, h));
    if (showVerticalGutter && w > gutterWidth * 2) {
      g2.draw(new Line2D.Float(x + gutterWidth, y, x + gutterWidth, y + h));
      g2.draw(new Line2D.Float(x + w - gutterWidth, y, x + w - gutterWidth, y + h));
    }
    if (adapter != null) {
      adapter.drawHorizontalDividers(g2);
    }
    drawSubShapesOutline(g2);
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    super.drawOutline(g2, x1, y1, x2, y2);
    if (adapter != null) {
      float x = Math.min(x1, x2);
      float y = Math.min(y1, y2);
      float h = Math.abs(y1 - y2);
      float w = Math.abs(x1 - x2);
      adapter.drawHorizontalDividers(g2, x, y, w, h);
    }
  }

  @Override
  public void drawFrameEx(Graphics2D g2, float marksize, 
			  boolean fill, Color frameColor) {
    if (adapter != null) {
      adapter.drawFrame(g2, marksize, fill, frameColor);
    }
  }

  public float[] getHorizontalDividers() {
    if (adapter != null) {
      return adapter.getHorizontalDividers();
    }
    return null;
  }

  public void setHorizontalDividers(float[] dividers) {
    if (adapter != null) {
      adapter.setHorizontalDividers(dividers);
    }
  }

  public int getNumberOfHorizontalCompartments() {
    if (adapter != null) {
      return adapter.getNumberOfHorizontalCompartments();
    }
    return 1;
  }

  public Rectangle2D getHorizontalCompartmentBounds(int i) {
    if (adapter != null) { 
      return adapter.getHorizontalCompartmentBounds(i);
    }
    return null;
  }

  public Point2D getHorizontalCompartmentCenter(int i) {
    if (adapter != null) { 
      return adapter.getHorizontalCompartmentCenter(i);
    }
    return null;
  }

  public float getHorizontalCompartmentHeight(int i) {
    if (adapter != null) { 
      return adapter.getHorizontalCompartmentHeight(i);
    }
    return 0;
  }

  public float getHorizontalCompartmentTop(int i) {
    if (adapter != null) { 
      return adapter.getHorizontalCompartmentTop(i);
    }
    return 0;
  }

  public boolean isNumberOfHorizontalCompartmentsFixed() {
    if (adapter != null) {
      return adapter.isNumberOfHorizontalCompartmentsFixed();
    }
    return false;
  }

  public void setNumberOfHorizontalCompartmentsFixed(boolean b) {
    if (adapter != null) {
      adapter.setNumberOfHorizontalCompartmentsFixed(b);
    }
  }

  public boolean isScaleTopCompartment() {
    if (adapter != null) {
      return adapter.isScaleTopCompartment();
    }
    return true;
  }

  public void setScaleTopCompartment(boolean b) {
    if (adapter != null) {
      adapter.setScaleTopCompartment(b);
    }
  }

  public int inHorizontalCompartment(DrawShape s) {
    if (adapter != null) {
      return adapter.inHorizontalCompartment(s);
    }
    return -1;
  }

  public void moveHorizontalDivider(int i, float dy) {
    if (adapter != null) {
      adapter.moveHorizontalDivider(i, dy);
      doLayout();
    }
  }

  public void addHorizontalDivider(int p, float dy) {
    if (adapter != null) {
      adapter.addHorizontalDivider(p, dy);
      doLayout();
    }
  }

  public void removeHorizontalDivider(int p) {
    if (adapter != null) {
      adapter.removeHorizontalDivider(p);
      doLayout();
    }
  }

  public void adjustHorizontalDividers(float dy, boolean top) {
    if (adapter != null) {
      adapter.adjustHorizontalDividers(dy, top);
    }
  }

  @Override
  public void moveCorner(int pos, float dx, float dy, boolean scaleSubshapes) {
    super.moveCorner(pos, dx, dy, scaleSubshapes);
    if (adapter != null && dy != 0) {
      boolean top = (pos == DrawShapeConstants.PointNWCorner || 
		     pos == DrawShapeConstants.PointNECorner);
      adjustHorizontalDividers(dy, top);
      doLayout();
    }
  }

  @Override
  public void moveEdge(int pos, float dx, float dy, boolean scaleSubshapes) {
    super.moveEdge(pos, dx, dy, scaleSubshapes);
    if (adapter != null && dy != 0) {
      boolean top = (pos == DrawShapeConstants.PointNorthEdge);
      adjustHorizontalDividers(dy, top);
      doLayout();
    }
  }

  public void setDividerStyle(DividerLineMode lineMode, int lineStyle) {
    if (adapter != null) {
      adapter.setDividerStyle(lineMode, lineStyle);
    }
  }

  public void setHighLightCompartments(boolean highlight) { 
    if (adapter != null) {
      adapter.setHighLightCompartments(highlight);
    }
  }

  public boolean isExpandable() {
    if (adapter != null) {
      return adapter.isExpandable();
    }
    return true;
  }

  public void setExpandable(boolean b) {
    if (adapter != null) {
      adapter.setExpandable(b);
    }
  }

  public boolean isCollapsable() {
    if (adapter != null) {
      return adapter.isCollapsable();
    }
    return true;
  }

  public void setCollapsable(boolean b) {
    if (adapter != null) {
      adapter.setCollapsable(b);
    }
  }

  // shape is not ncessisarily a subshape 
  public void positionShapeInCompartment(DrawShape shape, int i) {
    if (adapter != null) {
      adapter.positionShapeInCompartment(shape, i);
    }
  }

  public void setShapeInCompartment(DrawShape shape, int i) {
    int role = COMPARTMENT_ROLE_BASE + i;
    DrawShape s0 = findInRole(role);
    if (s0 != null) {
      removeShape(s0);
    }
    if (shape != null) {
      shape.setRole(role);
      addShape(shape);
      positionCompartmentContent(i);
    }
  }

  public DrawShape removeShapeInCompartment(int i) {
    int role = COMPARTMENT_ROLE_BASE + i;
    DrawShape s0 = findInRole(role);
    if (s0 != null) {
      removeShape(s0);
    }
    return s0;
  }

  public DrawShape getShapeInCompartment(int i) {
    int role = COMPARTMENT_ROLE_BASE + i;
    DrawShape shape = findInRole(role);
    return shape;
  }

  @Override
  public int isInside(float px, float py) {
    int result = super.isInside(px, py);
    if (result == PointInside) {
      if (adapter != null) {
	int d = adapter.onHorizontalDivider(px, py);
	if (d != -1) {
	  result = d;
	}
      }
    }
    return result;
  }

  @Override
  public void setLabelOffset() {
    Label label = findLabel();
    if (label != null) {
      if (label.getRole() == ROLE_NODE_TITLE_LABEL && adapter != null) {
	float[] div = adapter.getHorizontalDividers();
	if (div != null && div.length > 0) {
	  float x = (float) bounds.getX();
	  float y = (float) bounds.getY();
	  float w = (float) bounds.getWidth();
	  float h = (float) bounds.getHeight();
	  float h0 = div[0] * h;
	  setLabelOffset(x, y, w, h0);
	}
      } else if (label.getRole() == ROLE_NODE_LABEL) {
	setLabelOffset(bounds);
      } else {
	setLabelOffset(bounds);
      }
    }
  }

  @Override
  public void positionLabel() {
    Label label = findLabel();
    if (label != null) {
      if (label.getRole() == ROLE_NODE_TITLE_LABEL) {
	float x = (float) Math.min(p1.getX(), p2.getX());
	float y = (float) Math.min(p1.getY(), p2.getY());
	float w = (float) Math.abs(p1.getX() - p2.getX());
	float h = (float) Math.abs(p1.getY() - p2.getY());
	float[] div = adapter.getHorizontalDividers();

	float dh = 0;
	DrawShape tshape = findTemplateShape();
	if (tshape != null) {
	  Rectangle2D lb = label.getBounds();
	  Rectangle2D tb = tshape.getBounds();
	  float y1 = (float) tb.getY();
	  float x1 = (float) tb.getX();
	  float h1 = (float) tb.getHeight();
	  float lx = (float) lb.getX();
	  float lw = (float) lb.getWidth();
	  if ((lx + lw) > x1) {
	    dh = (y1 + h1) - y;
	  }
	}

	if (div != null && div.length > 0) {
	  float h0 = div[0] * h;
	  if (dh < 0 || dh > h0) {
	    dh = 0;
	  }
	  positionLabel(x, y + dh, w, h0 - dh);
	} else {
	  if (dh < 0 || dh > h) {
	    dh = 0;
	  }
	  positionLabel(x, y + dh, w, h - dh);
	}
      } else if (label.getRole() == ROLE_NODE_LABEL) {
	positionLabel(bounds);
      } else {
	positionLabel(bounds);
      }
    }
  }

  protected void positionCompartmentContent(int i) {
    if (adapter != null) {
      adapter.positionCompartmentContent(i);
    }
  }

  @Override
  public void layoutComponents() {
    fitCompartments(true); 
  }

  // adjust dividers 
  public void fitCompartments() {
    fitCompartments(true); 
  }

  // adjust dividers 
  public void fitCompartments(boolean keepWidth) {
    if (adapter != null) {
      adapter.fitCompartments(keepWidth, getTemplateXAdjust(), getTemplateYAdjust());
    }
  }

  @Override
  public void doLayout() {
    // System.out.println("CompartmentalRectangleShape.doLayout()");

    positionTemplateShape();
    positionLabel();
    positionBoundaryNodes();

    int n = adapter.getNumberOfHorizontalCompartments();
    for (int i = 0; i <= n; i++) {
      positionCompartmentContent(i);
    }

  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      if (adapter != null) {
	adapter.writeXML(out, namespacePrefix);
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String nodename = e.getLocalName();
      if ("Dividers".equals(nodename)) {
	if (adapter != null) {
	  adapter.setAttrFromXML(e, namespaceURI);
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  protected HorizontalCompartmentsAdapter adapter;

}
